#pragma once

/* selectServer */

#include <iostream>
#include <functional>
#include "sock.hpp"

namespace select_ns
{
    static const uint16_t default_port = 8080;
    static const int fd_num = sizeof(fd_set) * 8; // 文件描述符集最大容纳文件描述符个数
    static const int default_fd = -1;             // 文件描述符默认值

    using func_t = std::function<std::string(const std::string &)>;

    class SelectServer
    {
    public:
        SelectServer(func_t cb, uint16_t port = default_port)
            : _listen_sockfd(-1), _port(port), _fd_array(nullptr), _callback(cb)
        {
        }
        ~SelectServer()
        {
            if (_listen_sockfd < 0)
                close(_listen_sockfd);
            if (_fd_array)
                delete[] _fd_array;
        }

        void initServer()
        {
            // 创建套接字
            _listen_sockfd = Sock::Socket();
            // 绑定端口
            Sock::Bind(_listen_sockfd, _port);
            // 设置为监听状态
            Sock::Listen(_listen_sockfd);

            // 初始化文件描述符数组
            _fd_array = new int[fd_num]();
            for (int i = 0; i < fd_num; ++i)
                _fd_array[i] = default_fd;
            _fd_array[0] = _listen_sockfd; // 服务器初始只有listen套接字，所以这里只添加listen套接字
        }
        void start()
        {

            while (true)
            {
                // 获取连接

                /* 在select多路转接的写法中，不能直接在这里accept listen套接字，因为当没有连接请求时，accept也会被阻塞等待，所以这是阻塞式写法
                    accept = 等+获取连接 */
                // std::string clientip;
                // uint16_t clientport;
                // int sockfd = Sock::Accept(_listen_sockfd, &clientip, &clientport);
                // if(sockfd < 0) continue;

                /* select多路转接写法
                    等底层连接就绪后，再accept。
                    由select等待连接，accept只负责获取连接
                */

                // rfds是输入输出型参数，所以每次都要重置其内事件
                fd_set rfds;    // 读文件描述符集
                FD_ZERO(&rfds); // 初始化读文件描述符集

                /* 初始化最大文件描述符值和读文件描述符集 */
                int max_fd = _fd_array[0];
                for (int i = 0; i < fd_num; ++i)
                {
                    if (_fd_array[i] == default_fd)
                        continue;

                    // 添加合法文件描述符到文件描述符集中
                    FD_SET(_fd_array[i], &rfds);

                    // 设置最大文件描述符值
                    if (max_fd < _fd_array[i])
                        max_fd = _fd_array[i];
                }

                logMessage(NORMAL, "max_fd: %d", max_fd);

                // FD_SET(_listen_sockfd, &rfds); // 将listen套接字添加到读文件描述符集中，以待后面select监听等待list套接字

                // struct timeval timeout = {1, 0}; // 等待方式
                 // 注意，这里的timeval不能定义在while循环外。\
                timeout是输入输出型参数，如果定义在循环外，当出现超时返回，timeout里的阻塞等待时间就被减到0了，以后的任何等待就都是非阻塞等待了

                // int n = select(_listen_sockfd + 1, &rfds, nullptr, nullptr, &timeout); // bug?
                int n = select(max_fd + 1, &rfds, nullptr, nullptr, nullptr); // 阻塞式等待

                switch (n)
                {
                case 0: // 超时返回
                    logMessage(NORMAL, "timeout...");
                    break;
                case -1: // 出错
                    logMessage(WARNING, "select error, errno: %d, indiacte: %s", errno, strerror(errno));
                    break;
                default: // 有事件就绪
                    logMessage(NORMAL, "have event ready");

                    // logMessage(DEBUG, "调用HandleEvent");
                    // 处理事件
                    HandleEvent(rfds); // 就绪事件都在rfds中

                    break;
                }
                // 通信

                // sleep(1);
            }
        }

    private:
        void Accepter(int listensockfd)
        {
            // logMessage(DEBUG, "Accept前，_fd_array[0]: %d, listensock: %d", _fd_array[0], _listen_sockfd);
            std::string clientip;
            uint16_t clientport;
            int sockfd = Sock::Accept(_listen_sockfd, &clientip, &clientport);
            if (sockfd < 0)
                return;

            logMessage(NORMAL, "accept link from %s[%d] success", clientip.c_str(), clientport);
            // logMessage(DEBUG, "Accept后，_fd_array[0]: %d, listensock: %d", _fd_array[0], _listen_sockfd);

            /* 这里不能直接进行read/recv！因为不清楚底层有没有数据就绪，还是可能会被阻塞。
            在select多路转接中，只有select知道是否有数据就绪，所以，要将得到的新套接字交付给select监听是否有数据就绪
            将新文件描述符交付给select，只需将新文件描述符添加到_fd_array数组中即可，
            在上层调用select时会统一监管合法文件描述符，检测哪些事件已经就绪 */
            int i = 0;
            for (; i < fd_num; ++i)
            {
                if (_fd_array[i] != default_fd)
                    continue;
                else
                    break; // 不要直接在这里添加文件描述符，还要考虑容量是否满的问题
            }
            if (i == fd_num) // 文件描述符数组已满，直接断开当前连接
            {
                logMessage(WARNING, "server load is full, please wait...");
                close(sockfd);
            }
            else
            {
                _fd_array[i] = sockfd; // 添加新文件描述符到文件描述符数组
                // logMessage(DEBUG, "添加文件描述符%d到数组", sockfd);
            }
            // logMessage(DEBUG, "退出Accepter");
        }
        void Recver(int sockfd, int pos)
        {
            // logMessage(DEBUG, "Recver");

            // 读取request
            char buffer[1024];
            ssize_t n = recv(sockfd, buffer, sizeof(buffer) - 1, 0);
            if (n > 0)
            {
                buffer[n] = 0;
                logMessage(NORMAL, "client >> %s", buffer);
                // std::cout << "client >> " << buffer << std::endl;
            }
            else if (n == 0)
            {
                close(sockfd);

                _fd_array[pos] = default_fd;

                logMessage(NORMAL, "%s", "client quit");
                // std::cout << "client quit" << std::endl;
                return;
            }
            else
            {
                close(sockfd);

                _fd_array[pos] = default_fd;

                logMessage(ERROR, "client link error: %s", strerror(errno));
                // std::cerr << "client link error" << std::endl;
                return;
            }
            // 上面的写法是有问题的，不能保证读取到一个完整的报文。

            // 处理request 有问题
            std::string response = _callback(buffer);

            // 返回respone 有问题
            write(sockfd, response.c_str(), response.size());

            // logMessage(DEBUG, "退出Recver");
        }
        void HandleEvent(fd_set &rfds)
        {
            // logMessage(DEBUG, "fd_num: %d", fd_num);
            for (int i = 0; i < fd_num; ++i)
            {
                // 过滤掉非法文件描述符
                if (_fd_array[i] == default_fd)
                    continue;

                /* 走到这里一定是合法的文件描述符 */
                // logMessage(DEBUG, "_fd_array[0]: %d", _fd_array[0]);
                // 判断是否是listen套接字就绪
                if (FD_ISSET(_fd_array[i], &rfds) && _fd_array[i] == _listen_sockfd) /* 此处不能写FD_ISSET(_listen_sockfd, &rfds) !*/
                {
                    // 走到这里，listent套接字读事件已就绪
                    // logMessage(DEBUG, "调用Accepter");
                    Accepter(_listen_sockfd);
                }
                else if (FD_ISSET(_fd_array[i], &rfds)) // 其他IO事件就绪
                {
                    // logMessage(DEBUG, "调用recver");
                    Recver(_fd_array[i], i);
                }
                else// 事件未就绪
                {};
                // logMessage(DEBUG, "调用Accepter或Recver结束");
            }

            // logMessage(DEBUG, "打印合法文件描述符");
            Print();
        }
        void Print() // 测试用
        {
            std::cout << "fd_array: ";
            for (int i = 0; i < fd_num; ++i)
            {
                if (_fd_array[i] != default_fd)
                    std::cout << _fd_array[i] << " ";
            }
            std::cout << std::endl;
        }

    private:
        int _listen_sockfd;
        uint16_t _port;
        int *_fd_array;   /* 所有文件描述符。因为每次调用select都要重置文件描述符集，所以要将合法的文件描述符维护起来 */
        func_t _callback; // 处理方法，对收到的数据进行业务逻辑的处理
    };
}